package com.ma.implatform.service.impl;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ma.implatform.config.props.MinioProperties;
import com.ma.implatform.constant.Constant;
import com.ma.implatform.entity.FileInfo;
import com.ma.implatform.enums.FileType;
import com.ma.implatform.enums.ResultCode;
import com.ma.implatform.exception.GlobalException;
import com.ma.implatform.mapper.FileInfoMapper;
import com.ma.implatform.service.FileService;
import com.ma.implatform.session.SessionContext;
import com.ma.implatform.thirdparty.MinioService;
import com.ma.implatform.utils.ImageUtil;
import com.ma.implatform.vo.UploadImageVO;

import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import org.apache.commons.lang3.StringUtils;
import org.apache.tomcat.util.bcel.Const;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Date;
import java.util.Objects;

/**
 * 文件上传服务
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class FileServiceImpl extends ServiceImpl<FileInfoMapper, FileInfo> implements FileService {
    private final MinioService minioService;
    private final MinioProperties minioProperties;
    @PostConstruct
    public void init(){
        if (!minioService.bucketExists(minioProperties.getBucketName())){
            //创建bucket
            minioService.makeBucket(minioProperties.getBucketName());
            minioService.setBucketPublic(minioProperties.getBucketName());
        }
    }
    @Transactional
    @Override
    public String uploadFile(MultipartFile file) {
        try {
            Long userId = SessionContext.getSession().getUserId();
            //大小校验
            if (file.getSize()> Constant.MAX_FILE_SIZE){
                throw new GlobalException(ResultCode.PROGRAM_ERROR,"文件大小不能超过20M");
            }
            //如果文件已存在，直接复用
            String md5 = DigestUtils.md5DigestAsHex(file.getInputStream());
            FileInfo fileInfo = findByMd5(md5, FileType.FILE.code());
            if(!Objects.isNull(fileInfo)){
                //更新上传时间
                fileInfo.setUploadTime(new Date());
                this.updateById(fileInfo);
                //返回
                return fileInfo.getFilePath();
            }
            //上传
            String fileName = minioService.upload(minioProperties.getBucketName(), minioProperties.getFilePath(),file);
            if (StringUtils.isEmpty(fileName)){
                throw new GlobalException(ResultCode.XSS_PARAM_ERROR.getCode(),"文件上传失败");
            }
            String url = geneUrl(FileType.FILE,fileName);

            //保存文件
            saveFileInfo(file,md5,url);
            log.info("文件保存成功，用户id:{},url:{}",userId,url);
            return url;
        } catch (IOException e) {
             log.error("上传图片失败，{}",e.getMessage(),e);
             throw new GlobalException("图片上传失败");
        }
    }

    @Override
    public UploadImageVO uploadImage(MultipartFile file, Boolean isPermanent, Long thumbSize) {
        try{
            Long userId = SessionContext.getSession().getUserId();
            //大小检验
            if (file.getSize()>Constant.MAX_FILE_SIZE){
                throw new GlobalException("图片大小不能超过20M");
            }
            //图片格式校验

            UploadImageVO vo = new UploadImageVO();
            //获取图片长度和宽度
            BufferedImage bufferedImage = ImageIO.read(file.getInputStream());
            if (!Objects.isNull(bufferedImage)){
                vo.setWidth(bufferedImage.getWidth());
                vo.setHeight(bufferedImage.getHeight());
            }
            //如果文件已经存在，直接复用
            String md5 = DigestUtils.md5DigestAsHex(file.getInputStream());
            FileInfo fileInfo = findByMd5(md5, FileType.IMAGE.code());
            if (!Objects.isNull(fileInfo)){
                //更新上传时间和持久化标记
                fileInfo.setIsPermanent(isPermanent || fileInfo.getIsPermanent());
                fileInfo.setUploadTime(new Date());
                this.updateById(fileInfo);
                //返回
                vo.setOriginUrl(fileInfo.getFilePath());
                vo.setThumbUrl(fileInfo.getCompressedPath());
                return vo;
            }
            //上传原图
            String fileName = minioService.upload(minioProperties.getBucketName(),minioProperties.getImagePath(),file);
            if (StringUtils.isEmpty(fileName)){
                throw new GlobalException("图片上传失败");
            }
            vo.setOriginUrl(geneUrl(FileType.IMAGE,fileName));
            if (file.getSize()>thumbSize*1024){
                //大于50K的文件需上传缩略图
                byte[] imageByte = ImageUtil.compressForScale(file.getBytes(),thumbSize);
                String thumbFileName = minioService.upload(minioProperties.getBucketName(), minioProperties.getImagePath(),file.getOriginalFilename(),imageByte,file.getContentType());
                if (StringUtils.isEmpty(thumbFileName)){
                    throw new GlobalException("图片上传失败");
                }
                vo.setThumbUrl(geneUrl(FileType.IMAGE,thumbFileName));
                //保存文件信息
                saveImageFileInfo(file,md5,vo.getOriginUrl(),vo.getThumbUrl(),isPermanent);

            }else {
                //50K，用原图充当缩略图
                vo.setThumbUrl(geneUrl(FileType.IMAGE,fileName));
                //保存文件信息，由于缩略图不允许删除，此时原图也不允许删除
                saveImageFileInfo(file,md5,vo.getOriginUrl(),vo.getThumbUrl(),true);
            }
            log.info("文件图片成功，用户id:{},url:{}",userId,vo.getOriginUrl());
            return vo;
        }catch (Exception e){
            log.error("上传图片失败,{}",e.getMessage(),e);
            throw new GlobalException("文件上传失败");
        }
    }
    private String geneUrl(FileType fileType,String fileName){
        return StrUtil.join("/",minioProperties.getDomain(),minioProperties.getBucketName(),getBucketPath(fileType),fileName);
    }
    private String getBucketPath(FileType fileType){
        return switch (fileType){
            case FILE -> minioProperties.getFilePath();
            case IMAGE -> minioProperties.getImagePath();
            case VIDEO -> minioProperties.getVideoPath();
        };
    }
    private FileInfo findByMd5(String md5,Integer fileType){
        LambdaQueryWrapper<FileInfo> wrapper = Wrappers.lambdaQuery();
        wrapper.eq(FileInfo::getMd5,md5);
        wrapper.eq(FileInfo::getFileType,fileType);
        return getOne(wrapper);
    }
    private void saveImageFileInfo(MultipartFile file,String md5,String filePath,String compressedPath,Boolean isPermanent){
        FileInfo fileInfo = new FileInfo();
        fileInfo.setFileName(file.getOriginalFilename());
        fileInfo.setFileSize(file.getSize());
        fileInfo.setFileType(FileType.IMAGE.code());
        fileInfo.setCompressedPath(compressedPath);
        fileInfo.setMd5(md5);
        fileInfo.setFilePath(filePath);
        fileInfo.setIsPermanent(isPermanent);
        fileInfo.setUploadTime(new Date());
        this.save(fileInfo);
    }
    private void saveFileInfo(MultipartFile file, String md5, String filePath) throws IOException {
        FileInfo fileInfo = new FileInfo();
        fileInfo.setFileName(file.getOriginalFilename());
        fileInfo.setFileSize(file.getSize());
        fileInfo.setFileType(FileType.FILE.code());
        fileInfo.setFilePath(filePath);
        fileInfo.setMd5(md5);
        fileInfo.setIsPermanent(false);
        fileInfo.setUploadTime(new Date());
        this.save(fileInfo);
    }

}
